//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <stdio.h>
#include <winhack.h>
#include <elasys_server.h>
#include <aura.h>
#include <filedisk.h>
#include <driver.h>

#define Lock(hLock)   do { AuraCriticalSection_Enter(hLock); } while(0)
#define Unlock(hLock) do { AuraCriticalSection_Leave(hLock); } while(0)

// #define DEFAULTDISKNAME     L"disk.rom"
#define INVALID_FILE_SIZE ((Int64)-1)

CFileDisk:: CFileDisk(WString name)
{
   m_hLock = NULL;
   m_hDisk = NULL;
   m_diskSize = 0;
   
   assert(name.GetLength() < 32);
   m_diskName.Copy(name);
}

CFileDisk::~CFileDisk()
{
    if (NULL != m_hDisk) {
        CloseHandle(m_hDisk);
        m_hDisk = NULL;
        m_diskSize = 0;
    }
    if (NULL != m_hLock) {
        AuraCriticalSection_Destroy(m_hLock);
        m_hLock = NULL;
    }
}

ECode CFileDisk::Initialize()
{
    ECode ec = NOERROR;

    ec = AuraCriticalSection_Create((Aura_Handle *)&m_hLock);
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    ec = OpenWithUnlocked();
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    return NOERROR;

ErrorExit:
    if (NULL != m_hLock) {
        AuraCriticalSection_Destroy(m_hLock);
        m_hLock = NULL;
    }
    return ec;
}

ECode CFileDisk::OpenWithUnlocked()
{
     m_hDisk = CreateFileW((LPCWSTR)(wchar_t *)m_diskName,
                       GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ|FILE_SHARE_WRITE,
                       NULL,
                       OPEN_EXISTING,
                       0,
                       NULL);
    if (INVALID_HANDLE_VALUE == m_hDisk) {
        m_hDisk = NULL;
        // todo;
        return E_FAIL;
    }

    m_diskSize = (Int64)GetFileSize(m_hDisk, NULL);
    if ((INVALID_FILE_SIZE == m_diskSize)
       || ( 0 == m_diskSize)){ // impossible size 0.
        CloseHandle(m_hDisk);
        m_hDisk = NULL;
        m_diskSize = 0;
        return E_FAIL;
    }
    return NOERROR;
}

void CFileDisk::CloseWithUnlocked()
{
    if (NULL != m_hDisk) {
    CloseHandle(m_hDisk);
    m_hDisk = NULL;
    m_diskSize = 0;  
    }
}

ECode CFileDisk::Read(
    /* [in] */ Int64 offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = NOERROR;

    assert((bytesToRead & 0x1ff) == 0);
    if (NULL == pBuffer) {
        return E_INVALID_ARGUMENT;
    }

    Lock(m_hLock);
    // if the disk was closed, the size should be 0.
    if ((offset + bytesToRead) > m_diskSize) {
        ec = E_INVALID_OPERATION;
        goto ErrorExit;
    }

    {
        DWORD position;
        BOOL  bSuccess = FALSE;
        DWORD bytesRead;

        assert(NULL != m_hDisk);
        position = SetFilePointer(m_hDisk,
                             (LONG)offset,
                             NULL,
                             FILE_BEGIN);
        if (DWORD(-1) == position) {
           // TOOD;
        	printf("set file pointer fail.\n");
            ec = E_FAIL;
            goto ErrorExit;
        }
        bSuccess = ReadFile(m_hDisk,
                        pBuffer->GetPayload(),
                        bytesToRead,
                        (LPDWORD)&bytesRead,
                        NULL);
        if (!bSuccess) {
            // todo;
        	printf("read file fail.\n");
            ec = E_FAIL;
            goto ErrorExit;
        }
        pBuffer->SetUsed(bytesRead);
    }

    Unlock(m_hLock);
    return NOERROR;

ErrorExit:
    Unlock(m_hLock);
    return ec;
}

ECode CFileDisk::Write(
    /* [in] */ Int64 offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = NOERROR;

    assert((buffer.GetUsed() & 0x1ff) == 0);

    Lock(m_hLock);
    // if the disk was closed, the disk size should be 0.
    if ((offset + buffer.GetUsed()) > m_diskSize) {
        ec = E_INVALID_OPERATION;
        goto ErrorExit;
    }

    {
        DWORD position = -1;
        BOOL  bSuccess = FALSE;
        DWORD bytesWritten;

        assert(NULL != m_hDisk);
        position = SetFilePointer(m_hDisk,
                             (LONG)offset,
                             NULL,
                             FILE_BEGIN);
        if (DWORD(-1) == position) {
           // TOOD;
        	printf("set file pointer fail.\n");
            ec = E_FAIL;
            goto ErrorExit;
        }

        bSuccess = WriteFile(m_hDisk,
                            buffer.GetPayload(),
                            buffer.GetUsed(),
                            &bytesWritten,
                            NULL);
        if (!bSuccess) {
            // todo;
        	printf("write file fail %d \n", GetLastError());
            ec = E_FAIL;
            goto ErrorExit;
        }

        bSuccess = FlushFileBuffers(m_hDisk);
        if (pBytesWritten) *pBytesWritten = bytesWritten;        
    }

    Unlock(m_hLock);
    return NOERROR;

ErrorExit:
     Unlock(m_hLock);
     return ec;
}

ECode CFileDisk::Control(
    /* [in] */ Handle32 controlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    ECode ec = NOERROR;
    UInt32 sdData = 0;

    switch (controlCode) {
    case FILEDISK_CONTROL_OPEN:
         Lock(m_hLock);
         if (NULL != m_hDisk) {
             Unlock(m_hLock);
             ec = E_ALREADY_EXIST;
             break;
         }
         ec = OpenWithUnlocked();
         if (FAILED(ec)) {
             Unlock(m_hLock);
             break;
         }
         Unlock(m_hLock);

         sdData = 0xC0000001;//Insert
         EventNotify(EVENT_MiniSD, 0, &sdData);
         break;

    case FILEDISK_CONTROL_CLOSE:
         Lock(m_hLock);
         if (NULL != m_hDisk) {
             CloseWithUnlocked();  //BUGBUG: Can Remove disk before FS Cache flush?
         }
         Unlock(m_hLock);

         sdData = 0xC0000002;//Remove
         EventNotify(EVENT_MiniSD, 0, &sdData);
         break;

    case FILEDISK_CONTROL_GETSIZE:
         if ((NULL == pOutBuffer)
           || (pOutBuffer->GetCapacity() < sizeof(Int64))) {
             ec = E_INVALID_ARGUMENT;
         }
         Lock(m_hLock);
         pOutBuffer->Copy((Byte *)&m_diskSize, sizeof(Int64));
         Unlock(m_hLock);
         break;
    
    case FTLControl_DeleteBlock:
    	// todo
    	break;
    default:
        assert(0);
        break;
    }
    return ec;
}

ECode CFileDisk::S_Create(
        /* [in] */ WString name,
        /* [out] */ IDeviceDriver ** ppDisk)
{
    CFileDisk * pDisk = NULL;
    ECode ec = NOERROR;

    if (NULL == ppDisk) {
        return E_INVALID_ARGUMENT;
    }

    pDisk = new CFileDisk(name);
    if (NULL == pDisk) {
        return E_OUT_OF_MEMORY;
    }
    ec = pDisk->Initialize();
    if (FAILED(ec)) {
        delete pDisk;
    }
    pDisk->AddRef();
    *ppDisk = (IDeviceDriver *)pDisk;

    return ec;
}

